package com.like;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.VectorElement;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.List;
import java.util.Locale;

/**
 * Like button with animation effect
 */
public class LikeButton extends StackLayout implements Component.ClickedListener, Component.TouchEventListener {
    private Image icon;
    private DotsView dotsView;
    private CircleView circleView;
    private Icon currentIcon;
    private OnLikeListener likeListener;
    private OnAnimationEndListener animationEndListener;
    private int circleStartColor;
    private int circleEndColor;
    private int iconSize;
    private Context context;
    private float animationScaleFactor;
    private boolean isChecked;
    private boolean isEnabled;
    private AnimatorGroup animatorSet;

    private Element likeDrawable;
    private Element unLikeDrawable;

    public LikeButton(Context context) {
        super(context);
    }

    public LikeButton(Context context, AttrSet attrSet) {
        super(context, attrSet);
        this.context = context;
        init(context, attrSet);
    }

    public LikeButton(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        this.context = context;
        init(context, attrSet);
    }


    /**
     * Does all the initial setup of the button such as retrieving all the attributes that were
     * set in xml and inflating the like button's view and initial state.
     *
     * @param context context
     * @param attrs   attrs
     */
    private void init(Context context, AttrSet attrs) {
        Component component = LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_likeview, this, false);
        this.addComponent(component);
        icon = (Image) this.findComponentById(ResourceTable.Id_icon);
        dotsView = (DotsView) this.findComponentById(ResourceTable.Id_dots);
        circleView = (CircleView) this.findComponentById(ResourceTable.Id_circle);

        new StyledAttributes(attrs);
        setClickedListener(this::onClick);
    }

    /**
     * This triggers the entire functionality of the button such as icon changes,
     * animations, listeners etc.
     *
     * @param component component
     */
    @Override
    public void onClick(Component component) {
        if (!isEnabled) {
            return;
        }

        isChecked = !isChecked;

        icon.setImageElement(isChecked ? likeDrawable : unLikeDrawable);

        if (likeListener != null) {
            if (isChecked) {
                likeListener.liked(this);
            } else {
                likeListener.unLiked(this);
            }
        }

        if (animatorSet != null) {
            animatorSet.cancel();
        }

        if (isChecked) {
            icon.createAnimatorProperty().cancel();
            icon.setScaleX(0);
            icon.setScaleY(0);
            circleView.setInnerCircleRadiusProgress(0f);
            circleView.setOuterCircleRadiusProgress(0f);
            dotsView.setCurrentProgress(0f);

            animatorSet = new AnimatorGroup();

            ValueAnimator outerCircleAnimator = ValueAnimator.ofFloat(0f, 1f);
            outerCircleAnimator.setDuration(250);
            outerCircleAnimator.setCurveType(Animator.CurveType.DECELERATE);
            outerCircleAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    circleView.setOuterCircleRadiusProgress(value);
                }
            });

            ValueAnimator innerCircleAnimator = ValueAnimator.ofFloat(0f, 1f);
            innerCircleAnimator.setDuration(200);
            innerCircleAnimator.setDelay(200);
            innerCircleAnimator.setCurveType(Animator.CurveType.DECELERATE);
            innerCircleAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    circleView.setInnerCircleRadiusProgress(value);
                }
            });

            AnimatorProperty starScaleYAnimator = icon.createAnimatorProperty();
            starScaleYAnimator.setDuration(350);
            starScaleYAnimator.setDelay(250);
            starScaleYAnimator.scaleYFrom(0.2f);
            starScaleYAnimator.scaleY(1f);
            starScaleYAnimator.setCurveType(Animator.CurveType.OVERSHOOT);

            AnimatorProperty starScaleXAnimator = icon.createAnimatorProperty();
            starScaleXAnimator.setDuration(350);
            starScaleXAnimator.scaleXFrom(0.2f);
            starScaleXAnimator.scaleX(1f);
            starScaleXAnimator.setDelay(250);
            starScaleXAnimator.setCurveType(Animator.CurveType.OVERSHOOT);

            ValueAnimator dotsAnimator = ValueAnimator.ofFloat(0f, 1f);
            dotsAnimator.setDuration(900);
            dotsAnimator.setDelay(50);
            dotsAnimator.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
            dotsAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    dotsView.setCurrentProgress(value);
                }
            });

            animatorSet.runParallel(
                    outerCircleAnimator,
                    innerCircleAnimator,
                    starScaleYAnimator,
                    starScaleXAnimator,
                    dotsAnimator
            );

            animatorSet.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {
                    circleView.setInnerCircleRadiusProgress(0);
                    circleView.setOuterCircleRadiusProgress(0);
                    dotsView.setCurrentProgress(0);
                    icon.setScaleX(1);
                    icon.setScaleY(1);
                }

                @Override
                public void onEnd(Animator animator) {
                    if (animationEndListener != null) {
                        animationEndListener.onAnimationEnd(LikeButton.this);
                    }
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });
            animatorSet.start();
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (!isEnabled) {
            return true;
        }
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                /*
                Commented out this line and moved the animation effect to the action up event due to
                conflicts that were occurring when library is used in sliding type views.

                icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
                */
                setPressState(true);
                break;

            case TouchEvent.POINT_MOVE:
                float pivotX = component.getPivotX();
                float pivotY = component.getPivotY();
                boolean isInside = (pivotX > 0 && pivotX < getWidth() && pivotY > 0 && pivotY < getHeight());
                if (isPressed() != isInside) {
                    setPressState(isInside);
                }
                break;

            case TouchEvent.PRIMARY_POINT_UP:
                icon.createAnimatorProperty().scaleX(0.7f).scaleY(0.7f).setDuration(150)
                        .setCurveType(Animator.CurveType.DECELERATE).start();
                icon.createAnimatorProperty().scaleX(1).scaleY(1).setCurveType(Animator.CurveType.DECELERATE).start();
                if (isPressed()) {
                    callOnClick();
                    setPressState(false);
                }
                break;
            case TouchEvent.CANCEL:
                setPressState(false);
                break;
        }
        return true;
    }


    /**
     * This drawable is shown when the button is a liked state.
     *
     * @param resId resId
     */
    public void setLikeDrawableRes(int resId) {
        likeDrawable = Utils.getElementByResId(context, resId);

        if (iconSize != 0) {
            likeDrawable = updateIconSize(likeDrawable);
        }

        if (isChecked) {
            icon.setImageElement(likeDrawable);
        }
    }

    /**
     * This drawable is shown when the button is in a liked state.
     *
     * @param likeDrawable likeDrawable
     */
    public void setLikeDrawable(Element likeDrawable) {
        this.likeDrawable = likeDrawable;

        if (iconSize != 0) {
            this.likeDrawable = updateIconSize(likeDrawable);
        }

        if (isChecked) {
            icon.setImageElement(this.likeDrawable);
        }
    }

    /**
     * This drawable will be shown when the button is in on unLiked state.
     *
     * @param resId resId
     */
    public void setUnlikeDrawableRes(int resId) {
        unLikeDrawable = Utils.getElementByResId(context, resId);

        if (iconSize != 0) {
            unLikeDrawable = updateIconSize(unLikeDrawable);
        }

        if (!isChecked) {
            icon.setImageElement(unLikeDrawable);
        }
    }

    /**
     * This drawable will be shown when the button is in on unLiked state.
     *
     * @param unLikeDrawable unLikeDrawable
     */
    public void setUnlikeDrawable(Element unLikeDrawable) {
        this.unLikeDrawable = unLikeDrawable;

        if (iconSize != 0) {
            this.unLikeDrawable = updateIconSize(unLikeDrawable);
        }

        if (!isChecked) {
            icon.setImageElement(this.unLikeDrawable);
        }
    }

    /**
     * Sets one of the three icons that are bundled with the library.
     *
     * @param currentIconType currentIconType
     */
    public void setIcon(IconType currentIconType) {
        currentIcon = parseIconType(currentIconType);
        setLikeDrawableRes(currentIcon.getOnIconResourceId());
        setUnlikeDrawableRes(currentIcon.getOffIconResourceId());
        icon.setImageElement(this.unLikeDrawable);
    }

    private void setIcon() {
        setLikeDrawableRes(currentIcon.getOnIconResourceId());
        setUnlikeDrawableRes(currentIcon.getOffIconResourceId());
        icon.setImageElement(this.unLikeDrawable);
    }

    /**
     * Sets the size of the drawable/icon that's being used. The views that generate
     * the like effect are also updated to reflect the size of the icon.
     *
     * @param iconSize iconSize
     */
    public void setIconSizeVp(int iconSize) {
        setIconSizePx((int) Utils.dipToPixels(getContext(), iconSize));
    }

    /**
     * Sets the size of the drawable/icon that's being used. The views that generate
     * the like effect are also updated to reflect the size of the icon.
     *
     * @param iconSize iconSize
     */
    public void setIconSizePx(int iconSize) {
        this.iconSize = iconSize;
        setEffectsViewSize();
        this.unLikeDrawable = updateIconSize(unLikeDrawable);
        this.likeDrawable = updateIconSize(likeDrawable);
    }

    private Element updateIconSize(Element element) {
        if (element instanceof PixelMapElement) {
            icon.setWidth(LayoutConfig.MATCH_CONTENT);
            icon.setHeight(LayoutConfig.MATCH_CONTENT);
            icon.invalidate();
            return Utils.resizeDrawable(element, iconSize, iconSize);
        } else {
            icon.setWidth(iconSize);
            icon.setHeight(iconSize);
            icon.invalidate();
            return element;
        }
    }

    /**
     * * Parses the specific icon based on string
     * version of its enum.
     * These icons are bundled with the library and
     * are accessed via objects that contain their
     * resource ids and an enum with their name.
     *
     * @param iconType iconType
     * @return Icon
     */
    private Icon parseIconType(String iconType) {
        List<Icon> icons = Utils.getIcons();

        for (Icon likeIcon : icons) {
            if (likeIcon.getIconType().name().toLowerCase(Locale.ROOT).equals(iconType.toLowerCase(Locale.ROOT))) {
                return likeIcon;
            }
        }

        throw new IllegalArgumentException("Correct icon type not specified.");
    }

    /**
     * Parses the specific icon based on it's type.
     * These icons are bundled with the library and
     * are accessed via objects that contain their
     * resource ids and an enum with their name.
     *
     * @param iconType iconType
     * @return Icon
     */
    private Icon parseIconType(IconType iconType) {
        List<Icon> icons = Utils.getIcons();

        for (Icon likeIcon : icons) {
            if (likeIcon.getIconType().equals(iconType)) {
                return likeIcon;
            }
        }

        throw new IllegalArgumentException("Correct icon type not specified.");
    }

    /**
     * Listener that is triggered once the
     * button is in a liked or unLiked state
     *
     * @param likeListener likeListener
     */
    public void setOnLikeListener(OnLikeListener likeListener) {
        this.likeListener = likeListener;
    }

    /**
     * Listener that is triggered once the
     * button animation is completed
     *
     * @param animationEndListener animationEndListener
     */
    public void setOnAnimationEndListener(OnAnimationEndListener animationEndListener) {
        this.animationEndListener = animationEndListener;
    }


    /**
     * This set sets the colours that are used for the little dots
     * that will be exploding once the like button is clicked.
     *
     * @param primaryColor   primaryColor
     * @param secondaryColor secondaryColor
     */
    public void setExplodingDotColorsRes(int primaryColor, int secondaryColor) {
        dotsView.setColors(context.getColor(primaryColor), context.getColor(secondaryColor));
    }

    /**
     * This set sets the colours that are used for the little dots
     * that will be exploding once the like button is clicked.
     *
     * @param primaryColor   primaryColor
     * @param secondaryColor secondaryColor
     */
    public void setExplodingDotColorsInt(int primaryColor, int secondaryColor) {
        dotsView.setColors(primaryColor, secondaryColor);
    }

    /**
     * Set circle start color res
     *
     * @param circleStartColor circleStartColor
     */
    public void setCircleStartColorRes(int circleStartColor) {
        this.circleStartColor = context.getColor(circleStartColor);
        circleView.setStartColor(this.circleStartColor);
    }

    /**
     * Set circle start color
     *
     * @param circleStartColor circleStartColor
     */
    public void setCircleStartColorInt(int circleStartColor) {
        this.circleStartColor = circleStartColor;
        circleView.setStartColor(circleStartColor);
    }

    /**
     * Set circle start color res
     *
     * @param circleEndColor circleStartColor
     */
    public void setCircleEndColorRes(int circleEndColor) {
        this.circleEndColor = context.getColor(circleEndColor);
        circleView.setEndColor(this.circleEndColor);
    }

    /**
     * Set circle End color
     *
     * @param circleEndColor circleEndColor
     */
    public void setCircleSEndColorInt(int circleEndColor) {
        this.circleEndColor = circleEndColor;
        circleView.setStartColor(circleEndColor);
    }

    /**
     * This function updates the dots view and the circle
     * view with the respective sizes based on the size
     * of the icon being used.
     */
    private void setEffectsViewSize() {
        if (iconSize != 0) {
            dotsView.setSize((int) (iconSize * animationScaleFactor), (int) (iconSize * animationScaleFactor));
            circleView.setSize(iconSize, iconSize);
        }
    }

    /**
     * Sets the initial state of the button to liked
     * or unliked.
     *
     * @param status like / unlike
     */
    public void setLiked(Boolean status) {
        if (status) {
            isChecked = true;
            icon.setImageElement(likeDrawable);
        } else {
            isChecked = false;
            icon.setImageElement(unLikeDrawable);
        }
    }

    /**
     * Returns current like state
     *
     * @return current like state
     */
    public boolean isLiked() {
        return isChecked;
    }

    @Override
    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }

    /**
     * Sets the factor by which the dots should be sized.
     *
     * @param animationScaleFactor animationScaleFactor
     */
    public void setAnimationScaleFactor(float animationScaleFactor) {
        this.animationScaleFactor = animationScaleFactor;
        setEffectsViewSize();
    }

    /**
     * StyledAttributes
     */
    private class StyledAttributes {
        private String icon_size = "icon_size";

        private String icon_type = "icon_type";

        private String like_drawable = "like_drawable";

        private String unlike_drawable = "unlike_drawable";

        private String circle_start_color = "circle_start_color";

        private String circle_end_color = "circle_end_color";

        private String dots_primary_color = "dots_primary_color";

        private String dots_secondary_color = "dots_secondary_color";

        private String is_enabled = "is_enabled";

        private String liked = "liked";

        private String anim_scale_factor = "anim_scale_factor";


        StyledAttributes(AttrSet attrs) {
            iconSize = attrs.getAttr(icon_size).isPresent() ? attrs.getAttr(icon_size).get().getDimensionValue() : -1;
            if (iconSize == -1) {
                iconSize = 100;
            }

            if (attrs.getAttr(like_drawable).isPresent()) {
                try {
                    likeDrawable = attrs.getAttr(like_drawable).get().getElement();
                } catch (Exception e) {
                    String stringValue = attrs.getAttr(like_drawable).get().getStringValue();
                    int res = Integer.parseInt(stringValue.split(":")[1]);
                    likeDrawable = Utils.getElementByResId(context, res);
                }
            }

            if (likeDrawable != null) {
                setLikeDrawable(likeDrawable);
            }

            if (attrs.getAttr(unlike_drawable).isPresent()) {
                try {
                    unLikeDrawable = attrs.getAttr(unlike_drawable).get().getElement();
                } catch (Exception e) {
                    String stringValue = attrs.getAttr(unlike_drawable).get().getStringValue();
                    int res = Integer.parseInt(stringValue.split(":")[1]);
                    unLikeDrawable = Utils.getElementByResId(context, res);
                }
            }

            if (unLikeDrawable != null) {
                setUnlikeDrawable(unLikeDrawable);
            }

            String iconType = attrs.getAttr(icon_type).isPresent() ?
                    attrs.getAttr(icon_type).get().getStringValue() : null;

            if (iconType != null) {
                if (!iconType.isEmpty()) {
                    currentIcon = parseIconType(iconType);
                }
            }

            if (attrs.getAttr(circle_start_color).isPresent()) {
                Color color = attrs.getAttr(circle_start_color).get().getColorValue();
                if (color == null) {
                    String str = attrs.getAttr(circle_start_color).get().getStringValue();
                    circleStartColor = Color.getIntColor(str.split(":")[1]);
                } else {
                    circleStartColor = color.getValue();
                }
            } else {
                circleStartColor = 0;
            }

            if (circleStartColor != 0) {
                circleView.setStartColor(circleStartColor);
            }

            if (attrs.getAttr(circle_end_color).isPresent()) {
                Color color = attrs.getAttr(circle_end_color).get().getColorValue();
                if (color == null) {
                    String str = attrs.getAttr(circle_end_color).get().getStringValue();
                    circleEndColor = Color.getIntColor(str.split(":")[1]);
                } else {
                    circleEndColor = color.getValue();
                }
            } else {
                circleEndColor = 0;
            }

            if (circleEndColor != 0) {
                circleView.setEndColor(circleEndColor);
            }

            int dotPrimaryColor;

            if (attrs.getAttr(dots_primary_color).isPresent()) {
                Color color = attrs.getAttr(dots_primary_color).get().getColorValue();
                if (color == null) {
                    String str = attrs.getAttr(dots_primary_color).get().getStringValue();
                    dotPrimaryColor = Color.getIntColor(str.split(":")[1]);
                } else {
                    dotPrimaryColor = color.getValue();
                }
            } else {
                dotPrimaryColor = 0;
            }

            int dotSecondaryColor;

            if (attrs.getAttr(dots_secondary_color).isPresent()) {
                Color color = attrs.getAttr(dots_secondary_color).get().getColorValue();
                if (color == null) {
                    String str = attrs.getAttr(dots_secondary_color).get().getStringValue();
                    dotSecondaryColor = Color.getIntColor(str.split(":")[1]);
                } else {
                    dotSecondaryColor = color.getValue();
                }
            } else {
                dotSecondaryColor = 0;
            }

            if (dotPrimaryColor != 0 && dotSecondaryColor != 0) {
                dotsView.setColors(dotPrimaryColor, dotSecondaryColor);
            }

            if (likeDrawable == null && unLikeDrawable == null) {
                if (currentIcon != null) {
                    setIcon();
                } else {
                    setIcon(IconType.Heart);
                }
            }

            setEnabled(attrs.getAttr(is_enabled).isPresent() ? attrs.getAttr(is_enabled).get().getBoolValue() : true);
            Boolean status = attrs.getAttr(liked).isPresent() ? attrs.getAttr(liked).get().getBoolValue() : false;
            String factor = attrs.getAttr(anim_scale_factor).isPresent()
                    ? attrs.getAttr(anim_scale_factor).get().getStringValue() : "3";
            setAnimationScaleFactor(Float.parseFloat(factor));
            setLiked(status);
        }
    }
}
